package com.magenta.maxoptra.geocoding.nominatim;

import com.magenta.maxoptra.gis.commons.StringUtils;
import com.magenta.maxoptra.gis.GeoAddress;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import org.xmlpull.v1.XmlPullParserException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;

/**
 * Project: Santa-cruz
 * Author:  Irina Isaeva
 * Created: 09.06.12 12:17
 * <p/>
 * Copyright (c) 1999-2012 Magenta Corporation Ltd. All Rights Reserved.
 * Magenta Technology proprietary and confidential.
 * Use is subject to license terms.
 * <p/>
 * $Id$
 */
public class NominatimWS {

    private static final String UK = "UK";
    private static final String COUNTRY_CODES = "&countrycodes=gb";

    private static NominatimWS instanse = null;

    public static NominatimWS getInstance() {
        if (instanse == null) {
            instanse = new NominatimWS();
        }
        return instanse;
    }

    private NominatimWS() {
    }

    public GeoAddress getAddress(Proxy proxy, String url, double lat, double lon) throws IOException, ParserConfigurationException, SAXException, XmlPullParserException {
        GeoAddress address = new GeoAddress();
        DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
        String queryString = ("" + url + "reverse?" +
                "format=xml" +
                "&lat=" + lat +
                "&lon=" + lon +
                "&zoom=18" + // zoom=[0-18] Level of detail required where 0 is country and 18 is house/building
                "&addressdetails=1" +
                "&accept-language=en").replaceAll(",", ".").replaceAll(" ", "%");
        InputStream in = getStreem(proxy, org.apache.commons.httpclient.util.URIUtil.encodeQuery(queryString));
        DocumentBuilder docBuilder = domFactory.newDocumentBuilder();
        Document dom = docBuilder.parse(in);
        address.setLatitude(lat);
        address.setLongitude(lon);
        Element document = dom.getDocumentElement();
        if ("reversegeocode".equals(document.getNodeName())) {
            Element resultElement = findElement(document, "result");
            if (resultElement!=null) {
                address.setInfo(resultElement.getTextContent());
            }
            Element addresspartsElement = findElement(document, "addressparts");
            if (addresspartsElement!=null) {
                Element houseElement = findElement(addresspartsElement, "house");
                if (houseElement!=null) {
                    address.setHouse(houseElement.getTextContent());
                }
                Element roadElement = findElement(addresspartsElement, "road");
                if (roadElement!=null) {
                    address.setStreet(roadElement.getTextContent());
                }
                Element state = findElement(addresspartsElement, "state");
                if (state!=null) {
                    address.setState(state.getTextContent());
                }
                Element townElement = findElement(addresspartsElement, "town");
                if (townElement!=null) {
                    address.setCity(townElement.getTextContent());
                }
                Element cityElement = findElement(addresspartsElement, "city");
                if (cityElement!=null) {
                    address.setCity(cityElement.getTextContent());
                }
                Element countyElement = findElement(addresspartsElement, "county");
                if (countyElement!=null) {
                    address.setCounty(countyElement.getTextContent());
                }
                Element postcodeElement = findElement(addresspartsElement, "postcode");
                if (postcodeElement!=null) {
                    address.setZip(postcodeElement.getTextContent());
                }
                Element countryElement = findElement(addresspartsElement, "country");
                if (countryElement!=null) {
                    String text = countryElement.getTextContent();
                    if ("United Kingdom".equalsIgnoreCase(text)) {
                        address.setCountry(UK);
                    } else {
                        address.setCountry(text);
                    }
                }
            }
        }
        address.setSpeedLimit(70); //todo max limit in UK
        return address;
    }

    private Element findElement(Element parentElement, String elementName) {
        NodeList addresspartsList = parentElement.getElementsByTagName(elementName);
        if (addresspartsList != null) {
            for (int f = 0; f < addresspartsList.getLength(); f++) {
                Node node = addresspartsList.item(f);
                if (node instanceof Element) {
                    return (Element) node;
                }
            }
        }
        return null;
    }


    public List<GeoAddress> getCoordinate(Proxy proxy, String urlString, String keyWord, String countryCodes, int limit) throws IOException, ParserConfigurationException, SAXException, XmlPullParserException {
        List<GeoAddress> addressList = new ArrayList<GeoAddress>();
        if (StringUtils.isNotEmpty(keyWord)) {

            DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();

            String requestString = ((urlString + "search.php?" +
                    "q=" + keyWord + "&" +
                    "format=xml&" +
                    "polygon=0&" +
                    "addressdetails=1&" +
                    "accept-language=en" +
                    "&limit=" + limit +
                    ((StringUtils.isNotEmpty(countryCodes)) ? "&countrycodes=" + countryCodes : "")
            ).replaceAll(",", ".")).replaceAll(" ", "%");

            InputStream in = getStreem(proxy, org.apache.commons.httpclient.util.URIUtil.encodeQuery(requestString));
            DocumentBuilder docBuilder = domFactory.newDocumentBuilder();
            Document dom = docBuilder.parse(in);

            Element document = dom.getDocumentElement();
            if ("searchresults".equals(document.getNodeName())) {
                NodeList placeList = document.getElementsByTagName("place");
                if (placeList != null) {
                    for (int f = 0; f < placeList.getLength(); f++) {
                        Node placeNode = placeList.item(f);
                        if (placeNode instanceof Element) {
                            Element placeElement = (Element) placeNode;
                            GeoAddress address = new GeoAddress();
                            address.setLatitude(Double.parseDouble(placeElement.getAttribute("lat")));
                            address.setLongitude(Double.parseDouble(placeElement.getAttribute("lon")));
                            NodeList placeParametersList = placeElement.getChildNodes();
                            if (placeParametersList != null) {
                                for (int ls = 0; ls < placeParametersList.getLength(); ls++) {
                                    Node item = placeParametersList.item(ls);
                                    if (item instanceof Element) {
                                        Element lsElement = (Element) item;
                                        String nodeName = lsElement.getNodeName();
                                        String textContext = lsElement.getTextContent();
                                        if ("country".equalsIgnoreCase(nodeName)) {
                                            address.setCountry(textContext);
                                        } else if ("county".equalsIgnoreCase(nodeName)) {
                                            address.setCounty(textContext);
                                        } else if ("village".equalsIgnoreCase(nodeName)) {
                                            address.setCity(textContext);
                                        } else if ("hamlet".equalsIgnoreCase(nodeName)) {
                                            address.setCity(textContext);
                                        } else if ("state".equalsIgnoreCase(nodeName)) {
                                            address.setState(textContext);
                                        } else if ("road".equalsIgnoreCase(nodeName)) {
                                            address.setStreet(textContext);
                                        } else if ("postcode".equalsIgnoreCase(nodeName)) {
                                            address.setZip(textContext);
                                        } else if ("administrative".equalsIgnoreCase(nodeName)) {
                                            address.setDistrict(textContext);
                                        }
                                    }
                                }
                            }
                            addressList.add(address);
                        }
                    }
                }
            }
        }
        return addressList;
    }


    private InputStream getStreem(Proxy proxy, String requestString) throws IOException {
        URL url = new URL(requestString);
        URLConnection urlConnection = null;
        if (proxy != null) {
            urlConnection = url.openConnection(proxy);
        } else {
            urlConnection = url.openConnection();
        }
        return urlConnection.getInputStream();
    }

}
